package com.blue.http.client;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import javax.net.ssl.SSLContext;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;

import com.blue.http.util.HttpException;

/**
 * HTTP 简化调用
 * 
 * @author zhengj
 * @since 1.0 2016年11月13日
 */
public final class HttpInvoker implements DisposableBean
{
	private static Logger logger = LoggerFactory.getLogger(HttpInvoker.class);
	
	private final static int TIMEOUT = 60000;
	private final static int MAX_TOTAL = 200;
	private final static int MAX_PER_ROUTE = 200;
	
	private final PoolingHttpClientConnectionManager connMgr;
	private final CloseableHttpClient httpClient;
	
	public HttpInvoker()
	{
		this(true);
	}
	
	public HttpInvoker(boolean trust)
	{
		RegistryBuilder<ConnectionSocketFactory> registry = RegistryBuilder.create();
		registry.register("http", PlainConnectionSocketFactory.getSocketFactory());
		if (trust)
		{
			try
			{
				SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (chain, type) -> true).build();
				SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext);
				registry.register("https", sslSF);
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
		else
		{
			registry.register("https", SSLConnectionSocketFactory.getSocketFactory());
		}
		
		SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
		connMgr = new PoolingHttpClientConnectionManager(registry.build());
		connMgr.setDefaultSocketConfig(socketConfig);
		connMgr.setMaxTotal(MAX_TOTAL);
		connMgr.setDefaultMaxPerRoute(MAX_PER_ROUTE);
		
		RequestConfig requestConfig = RequestConfig.custom()
					.setCookieSpec(CookieSpecs.IGNORE_COOKIES)
					.setSocketTimeout(TIMEOUT)
					.setConnectTimeout(TIMEOUT)
					.setConnectionRequestTimeout(TIMEOUT)
					.build();
		
		httpClient = HttpClients.custom()
							.setConnectionManager(connMgr)
							.setDefaultRequestConfig(requestConfig)
							.build();
	}
	
	
	/**
	 * HTTP 调用 GET 方法
	 * 
	 * @param url 网址
	 * @param headers HTTP头数组
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String get(String url, Header...headers) throws IOException, HttpException
	{
		HttpGet request = new HttpGet(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		return this.request(request);
	}
	
	/**
	 * HTTP 调用 GET 方法
	 * 
	 * @param url 网址
	 * @param f 消费输入流
	 * @param headers HTTP头数组
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public void get(String url, Consumer<InputStream> f, Header...headers) throws IOException, HttpException
	{
		HttpGet request = new HttpGet(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		this.request(url, request, f);
	}
	
	/**
	 * HTTP 调用 POST 方法
	 * 
	 * @param url 网址
	 * @param param POST提交内容
	 * @param headers HTTP头数组
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String post(String url, String param, Header...headers) throws IOException, HttpException
	{
		StringEntity entity = new StringEntity(param, Consts.UTF_8);
		HttpPost request = new HttpPost(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		request.setEntity(entity);
		return this.request(request);
	}
	
	/**
	 * HTTP 调用 POST 方法
	 * 
	 * @param url 网址
	 * @param param POST提交内容
	 * @param f 消费输入流
	 * @param headers HTTP头数组
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public void post(String url, String param, Consumer<InputStream> f, Header...headers) throws IOException, HttpException
	{
		StringEntity entity = new StringEntity(param, Consts.UTF_8);
		HttpPost request = new HttpPost(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		request.setEntity(entity);
		this.request(url, request, f);
	}
	
	/**
	 * HTTP 调用 POST 方法
	 * 
	 * @param url 网址
	 * @param param POST提交内容
	 * @param headers HTTP头数组
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String post(String url, Map<String, Object> param, Header...headers) throws IOException, HttpException
	{
		List<NameValuePair> pairList = new ArrayList<>();
		for (Map.Entry<String, Object> entry : param.entrySet())
		{
			if (entry.getValue() != null)
			{
				pairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
			}
			else
			{
				pairList.add(new BasicNameValuePair(entry.getKey(), null));
			}
		}
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairList, Consts.UTF_8);
		HttpPost request = new HttpPost(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		request.setEntity(entity);
		return this.request(request);
	}
	
	/**
	 * HTTP 调用 POST 方法
	 * 
	 * @param url 网址
	 * @param param POST提交内容
	 * @param f 消费输入流
	 * @param headers HTTP头数组
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public void post(String url, Map<String, Object> param, Consumer<InputStream> f, Header...headers) throws IOException, HttpException
	{
		List<NameValuePair> pairList = new ArrayList<>();
		for (Map.Entry<String, Object> entry : param.entrySet())
		{
			pairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
		}
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairList, Consts.UTF_8);
		HttpPost request = new HttpPost(url);
		if (headers.length > 0)
		{
			request.setHeaders(headers);
		}
		request.setEntity(entity);
		this.request(url, request, f);
	}
	
	/**
	 * HTTP 上传文件
	 * 
	 * @param url 网址
	 * @param textParam 文本参数
	 * @param fileParam 文件参数
	 * @param headers HTTP头数组
	 * @return 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public String upload(String url, Map<String, Object> textParam, Map<String, File> fileParam, Header...headers) throws IOException, HttpException
	{
		MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
		if (textParam != null && !textParam.isEmpty())
		{
			for (Map.Entry<String, Object> entry : textParam.entrySet())
			{
				if (entry.getValue() != null)
				{
					builder.addTextBody(entry.getKey(), entry.getValue().toString());
				}
			}
		}
		if (fileParam != null && !fileParam.isEmpty())
		{
			for (Map.Entry<String, File> entry : fileParam.entrySet())
			{
				if (entry.getValue() != null)
				{
					builder.addBinaryBody(entry.getKey(), entry.getValue());
				}
			}
		}
		
		HttpEntity entity = builder.build();
		
		HttpPost request = new HttpPost(url);
		request.setEntity(entity);
		
		return this.request(request);
	}
	
	/**
	 * HTTP 下载文件
	 * 
	 * @param url 网址
	 * @param file 保存文件
	 * @param headers HTTP头数组
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	public DownloadResult download(String url, File file, Header...headers) throws IOException, HttpException
	{
		File dir = file.getParentFile();
		if (!dir.exists())
		{
			if (!dir.mkdirs())
				throw new IOException("创建目录失败：" + dir.getAbsolutePath());
		}
		
		DownloadResult result = new DownloadResult();
		HttpGet request = new HttpGet(url);
		try (CloseableHttpResponse response = httpClient.execute(request);)
		{
			StatusLine statusLine = response.getStatusLine();
			if (statusLine.getStatusCode() != HttpStatus.SC_OK) // http调用失败
				throw new HttpException(statusLine.getStatusCode(), "下载失败：" + statusLine.getReasonPhrase());
			
			HttpEntity entity = response.getEntity();
			if (!entity.isStreaming())
				throw new HttpException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "下载失败：" + EntityUtils.toString(entity, Consts.UTF_8));
				
			result.setSize((int)entity.getContentLength());
			Header type = entity.getContentType();
			logger.debug(type.toString());
			if (type != null)
			{
				int pos = type.getValue().indexOf(';');
				if (pos == -1)
					result.setMimeType(type.getValue());
				else
					result.setMimeType(type.getValue().substring(0, pos));
			}
			try (FileOutputStream fos = new FileOutputStream(file);)
			{
				entity.writeTo(fos);
			}
		}
		return result;
	}
	
	
	/**
	 * HTTP 统一请求
	 * 
	 * @param request 请求方式，GET或POST
	 * @return HTTP 调用返回字符串内容
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	private String request(HttpRequestBase request) throws IOException, HttpException
	{
		try (CloseableHttpResponse response = httpClient.execute(request);)
		{
			StatusLine statusLine = response.getStatusLine();
			String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
			
			if (statusLine.getStatusCode() != HttpStatus.SC_OK) // http调用失败
			{
				logger.error("错误信息：{}", result);
				throw new HttpException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
			}
			
			return result;
		}
	}
	
	/**
	 * HTTP 统一请求
	 * 
	 * @param url 网址
	 * @param request 请求方式，GET或POST
	 * @param f 消费输入流
	 * @throws IOException IO异常时抛出
	 * @throws HttpException HTTP调用异常时抛出
	 */
	private void request(String url, HttpRequestBase request, Consumer<InputStream> f) throws IOException, HttpException
	{
		try (CloseableHttpResponse response = httpClient.execute(request);)
		{
			StatusLine statusLine = response.getStatusLine();
			if (statusLine.getStatusCode() != HttpStatus.SC_OK) // http调用失败
				throw new HttpException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
			
			HttpEntity entity = response.getEntity();
			try (InputStream is = entity.getContent();)
			{
				f.accept(is);
			}
		}
	}
	
	public CloseableHttpClient getHttpClient()
	{
		return httpClient;
	}
	
	@Override
	public void destroy()
	{
		try
		{
			httpClient.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		connMgr.shutdown();
	}
}
